#region Usings

using System;
using System.Linq;
using log4net;
using Newtonsoft.Json;
using Karma.Framework.Core.Utils;
using InvokerConfiguration =
    Karma.Framework.Core.Services.Configuration.ServiceInvokerConfiguration;
using SerializationConfiguration = Karma.Framework.Core.Serialization.Configuration;
using En = Ninject.Core.Infrastructure.Ensure;

#endregion

namespace Karma.Framework.Core.Services.Invoker.Processor
{
    public class JsonProcessor : IProcessor
    {
        private static ILog LOG = LogManager.GetLogger(typeof (JsonProcessor).Name);

        private static JsonSerializerSettings SerilizationSettings = new JsonSerializerSettings
                                                                         {
                                                                             DefaultValueHandling = DefaultValueHandling.Ignore,
                                                                             MissingMemberHandling = MissingMemberHandling.Ignore,
                                                                             ObjectCreationHandling = ObjectCreationHandling.Replace,
                                                                             ReferenceLoopHandling = ReferenceLoopHandling.Ignore,
                                                                             NullValueHandling = NullValueHandling.Ignore
    };

        #region IProcessor Members

        public virtual ServiceRequest Decode(string requestStr)
        {
            En.ArgumentNotNull(requestStr, "Service request string.");
            var serviceRequest = JsonConvert.DeserializeObject<JsonServiceRequest>(requestStr);

            En.ArgumentNotNull(serviceRequest, "Service Request.");
            if (LOG.IsDebugEnabled)
            {
                LOG.Debug("Request arguments: ");
                LOG.Debug(string.Format("Service: {0}", serviceRequest.Service));
                LOG.Debug(string.Format("Method: {0}", serviceRequest.Method));
                LOG.Debug(string.Format("Parameters: {0}", serviceRequest.Parameters));
                LOG.Debug(string.Format("Depth: {0}", serviceRequest.Depth));
            }
            En.ArgumentNotNull(serviceRequest.Service, "Service name.");
            En.ArgumentNotNull(serviceRequest.Method, "Service method.");

            return new ServiceRequest
                       {
                           Depth = serviceRequest.Depth,
                           Service = serviceRequest.Service,
                           Method = serviceRequest.Method,
                           Parameters = ProcessParameters(serviceRequest),
                           SerializationType = serviceRequest.SerializationType
                       };
        }

        public virtual string Encode(ServiceRequest request, ServiceResponse response)
        {
            if (LOG.IsDebugEnabled)
            {
                LOG.Debug(string.Format("Encoding response [{0}], Result: {1}.", response, response.Result));
            }
            var settings = new JsonSerializerSettings
                               {
                                   NullValueHandling = NullValueHandling.Include,
                                   ReferenceLoopHandling = ReferenceLoopHandling.Ignore,
                                   Converters =
                                       SerializationConfiguration.EnumConverters
                               };
            if (LOG.IsDebugEnabled)
            {
                LOG.Debug(string.Format("Serializing with depth [{0}].", request.Depth));
            }
            var targetType = InvokerConfiguration.Instance.Services[request.Service];
            var method = ReflectionUtils.TryGetMethodInfo(targetType, request.Method, request.Parameters.Length);
            settings.Converters = ProcessorCache.GetSerializationConverters(targetType, method);
            settings.SerializationDepth = request.Depth == 0 ? -1 : request.Depth + 1;
            var jsonresult = JsonConvert.SerializeObject(
                request.SerializationType == SerializationType.Wraped
                    ? response
                    : response.Result, Formatting.Indented, settings);
            if (LOG.IsDebugEnabled)
            {
                LOG.Debug(string.Format("Results to be written [{0}].", jsonresult));
            }
            if (LOG.IsDebugEnabled)
            {
                LOG.Debug("Writing the results to the response stream.");
            }
            return jsonresult;
        }

        public virtual string Encode(ServiceResponse response)
        {
            if (LOG.IsDebugEnabled)
            {
                LOG.Debug(string.Format("Encoding response [{0}], Result: {1}.", response, response.Result));
            }
            var settings = new JsonSerializerSettings
                               {
                                   NullValueHandling = NullValueHandling.Include,
                                   ReferenceLoopHandling = ReferenceLoopHandling.Ignore,
                                   Converters =
                                       SerializationConfiguration.EnumConverters,
                                   SerializationDepth = -1
                               };
            var jsonresult = JsonConvert.SerializeObject(response, Formatting.Indented, settings);
            if (LOG.IsDebugEnabled)
            {
                LOG.Debug(string.Format("Results to be written [{0}].", jsonresult));
            }
            if (LOG.IsDebugEnabled)
            {
                LOG.Debug("Writing the results to the response stream.");
            }
            return jsonresult;
        }

        public void Initialize()
        {
            ProcessorCache.Initialize();
        }

        #endregion

        private object[] ProcessParameters(JsonServiceRequest serviceRequest)
        {
            if (LOG.IsDebugEnabled)
            {
                LOG.Debug(string.Format("Processing parameters for {0}.{1}",
                                        serviceRequest.Service, serviceRequest.Method));
            }
            object[] parameters;
            var targetType = InvokerConfiguration.Instance.Services[serviceRequest.Service];
            if (targetType == null)
            {
                throw new ArgumentException("The service {0} is not configured.");
            }

            var parametersArray = GetParameters(serviceRequest.Parameters);
            if (LOG.IsDebugEnabled)
            {
                LOG.Debug(string.Format("Parameters array: [{0}] [{1}].", parametersArray.Length,
                                        parametersArray.ItemsToString()));
            }
            var method = ReflectionUtils.TryGetMethodInfo(targetType, serviceRequest.Method, parametersArray.Length);
            if (LOG.IsDebugEnabled)
            {
                LOG.Debug(string.Format("Method info: {0}.", method));
            }
            var converters = ProcessorCache.GetDeserializationConverters(targetType, method);
            if (LOG.IsDebugEnabled)
            {
                LOG.Debug(string.Format("Converters array: [{0}] [{1}].", converters.Length, converters));
            }
            var parameterTypes = ReflectionUtils.GetParameterTypes(targetType, serviceRequest.Method,
                                                                   parametersArray.Length);
            if (LOG.IsDebugEnabled)
            {
                LOG.Debug(string.Format("Parameter types array: [{0}] [{1}].", parameterTypes.Length,
                                        parameterTypes.ItemsToString()));
            }

            // empty parameters
            if (parametersArray.Length == 0)
            {
                parameters = new object[] {};
            }
            else
            {
                if (parameterTypes.Length == 1)
                {
                    SerilizationSettings.Converters = converters;
                    parameters = new[]
                                     {
                                         JsonConvert.DeserializeObject(
                                             serviceRequest.Parameters.Content, parameterTypes[0], SerilizationSettings)
                                     };
                    SerilizationSettings.Converters = null;
                }
                else
                {
                    var simple = parameterTypes.Where(p => !(p.IsValueType || p.Equals(typeof (string))))
                                     .Count() == 0;

                    var temp = simple
                                   ?
                                       JsonConvert.DeserializeObject<string[]>(
                                           serviceRequest.Parameters.Content)
                                   :
                                       (from js in JsonConvert.DeserializeObject<JsonRaw[]>(
                                            serviceRequest.Parameters.Content)
                                        select js.Content).ToArray();

                    parameters = new object[temp.Length];
                    for (var idx = 0; idx < temp.Length; idx++)
                    {
                        var paramType = parameterTypes[idx];
                        parameters[idx] = (paramType.IsPrimitive ||
                                           paramType.Equals(typeof (string)))
                                              ? Convert.ChangeType(temp[idx], paramType)
                                              :
                                                  JsonConvert.DeserializeObject(temp[idx], paramType);
                    }
                }
            }

            if (LOG.IsDebugEnabled)
            {
                LOG.Debug(string.Format("Processed parameters array: [{0}] [{1}]", parameters.Length,
                                        parameters.ItemsToString()));
            }
            return parameters;
        }

        private JsonRaw[] GetParameters(JsonRaw parameters)
        {
            if (LOG.IsDebugEnabled)
            {
                LOG.Debug(string.Format("Parameters value: {0}", parameters.Content));
            }
            if (string.IsNullOrEmpty(parameters.Content) || parameters.Content.Equals("null"))
            {
                if (LOG.IsDebugEnabled)
                {
                    LOG.Debug("Empty parameters");
                }
                return new JsonRaw[0];
            }
            /* it's an array */
            if (parameters.Content.StartsWith("["))
            {
                if (LOG.IsDebugEnabled)
                {
                    LOG.Debug("Parameters is an array.");
                }
                return JsonConvert.DeserializeObject<JsonRaw[]>(parameters.Content);
            }
            /* it's an object */
            if (parameters.Content.StartsWith("{"))
            {
                if (LOG.IsDebugEnabled)
                {
                    LOG.Debug("Teh parameter is a single object.");
                }
                return new[] {parameters};
            }
            return new[] {parameters};
        }
    }
}